using Fusion.Utilities.IO;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using UnityEditor;
using UnityEngine;

namespace Fusion.Protobuf.Editor
{
    public class XmlNodeData
    {
        private string protoFile;
        private XmlNode node;

        public string ProtoFile { get => protoFile; }
        public XmlNode Node { get => node; }

        public XmlNodeData(string protoFile, XmlNode node)
        {
            this.protoFile = protoFile;
            this.node = node;
        }
    }

    public class FieldData
    {
        private List<string> imports;
        private string type;

        public FieldData(string type)
        {
            this.type = type;
            imports = new List<string>();
        }

        public FieldData(List<string> imports, string type)
        {
            this.imports = imports;
            this.type = type;
        }

        public void Add(string value)
        {
            imports.Add(value);
        }

        public string Type { get => type; }
        public List<string> Imports { get => imports; }
    }


    public class ProxyGenerator
    {
        private static string xmlPath = $"{Path.GetFullPath("ProxyXML")}";
        private static string proxyPath = $"{Application.dataPath}/Scripts/Proxy";
        private static string proxyDataPath = $"{xmlPath}/Protobuf";
#if FUSION_FRAMEWORK_PROJECT
        private static string proxyTempletePath = $"{Application.dataPath}/com.fusion.protobuf/Editor/ProxyTempletes";

#else
        private static string proxyTempletePath = $"{Path.GetFullPath("Packages/com.fusion.protobuf")}/Editor/ProxyTempletes";
#endif
        private static string protoTemplete;
        private static string messageTemplete;
        private static string fieldTemplete;
        private static string objectFieldTemplete;
        private static string proxyIndexTemplete;
        private static string proxyManagerTemplete;
        private static string proxyManager_InitializateTemplete;
        private static string setterFunctionTemplete;
        private static string fieldSetterTemplete;
        private static string objectFieldSetterTemplete;
        private static string listFieldSetterTemplete;
        private static string listObjectFieldSetterTemplete;
        private static string mapFieldSetterTemplete;
        private static string mapObjectFieldSetterTemplete;
        private static string keysCheckerTemplete;
        private static string containsCheckerTemplete;
        private static string keysGetterTemplete;
        private static string objectGetterTemplete;
        private static string containerKeyTemplete;

        private static XmlDocument mapperXml;
        private static XmlDocument protoMapperXml;

        private static string[] baseType = { 
            "int",
            "long",
            "float",
            "double",
            "bool",
            "string",
        };

        private static Dictionary<string, string> suffixs = new Dictionary<string, string> {
            { "CSharp", "cs" },
        };
        private static string currentType = null;

        [MenuItem("Proxy/Generate/CSharp", false, 100)]
        public static void GenerateProxyCSharp()
        {
            GenerateProxy("CSharp");
        }

        [MenuItem("Proxy/Generate/NetExtend", false, 200)]
        public static void GenerateNetExtend()
        {
            IOUtility.Write($"{proxyPath}/NetExtend.cs", netExtend);
        }

        private static string netExtend = @"using Fusion.Net.Proxy;
using Fusion.Net.TCP;
using Fusion.Net.UDP;
using Google.Protobuf;
using System.Buffers;
using System.Net;

namespace Fusion.Net
{
    public static class NetExtend
    {
        public static void Send(this TCPClient tcp, int code, IMessage message = null, ErrorMessage error = null)
        {
            ProtocolMessage protocolMessage = ProxyManager.Generate(code, message, error);
            byte[] rentArray;
            int length = protocolMessage.ToRentByteArray(out rentArray);
            tcp.Send(rentArray, length);
            protocolMessage.Recycle();
            ArrayPool<byte>.Shared.Return(rentArray);
        }

        public static void Send(this UDPClient udp, EndPoint ip, int code, IMessage message = null, ErrorMessage error = null)
        {
            ProtocolMessage protocolMessage = ProxyManager.Generate(code, message, error);
            byte[] rentArray;
            int length = protocolMessage.ToRentByteArray(out rentArray);
            udp.Send(ip, rentArray, length);
            protocolMessage.Recycle();
            ArrayPool<byte>.Shared.Return(rentArray);
        }

        public static void Send(this UDPClient udp, string ip, int port, int code, IMessage message = null, ErrorMessage error = null)
        {
            ProtocolMessage protocolMessage = ProxyManager.Generate(code, message, error);
            byte[] rentArray;
            int length = protocolMessage.ToRentByteArray(out rentArray);
            udp.Send(ip, port, rentArray, length);
            protocolMessage.Recycle();
            ArrayPool<byte>.Shared.Return(rentArray);
        }

        public static void SendBroadcast(this TCPServer tcp, int code, IMessage message = null, ErrorMessage error = null)
        {
            ProtocolMessage protocolMessage = ProxyManager.Generate(code, message, error);
            byte[] rentArray;
            int length = protocolMessage.ToRentByteArray(out rentArray);
            tcp.SendBroadcast(rentArray, length);
            protocolMessage.Recycle();
            ArrayPool<byte>.Shared.Return(rentArray);
        }

        public static void Send(this SocketBase socket, int code, IMessage message = null, ErrorMessage error = null)
        {
            ProtocolMessage protocolMessage = ProxyManager.Generate(code, message, error);
            byte[] rentArray;
            int length = protocolMessage.ToRentByteArray(out rentArray);
            socket.Send(rentArray, length);
            protocolMessage.Recycle();
            ArrayPool<byte>.Shared.Return(rentArray);
        }

        public static void Send(this TCPClient tcp, ProxyMessage message)
        {
            byte[] rentArray;
            int length = message.ToRentByteArray(out rentArray);
            tcp.Send(rentArray, length);
            message.Recycle();
            ArrayPool<byte>.Shared.Return(rentArray);
        }

        public static void Send(this UDPClient udp, EndPoint ip, ProxyMessage message)
        {
            byte[] rentArray;
            int length = message.ToRentByteArray(out rentArray);
            udp.Send(ip, rentArray, length);
            message.Recycle();
            ArrayPool<byte>.Shared.Return(rentArray);
        }

        public static void Send(this UDPClient udp, string ip, int port, ProxyMessage message)
        {
            byte[] rentArray;
            int length = message.ToRentByteArray(out rentArray);
            udp.Send(ip, port, rentArray, length);
            message.Recycle();
            ArrayPool<byte>.Shared.Return(rentArray);
        }

        public static void SendBroadcast(this TCPServer tcp, ProxyMessage message)
        {
            byte[] rentArray;
            int length = message.ToRentByteArray(out rentArray);
            tcp.SendBroadcast(rentArray, length);
            message.Recycle();
            ArrayPool<byte>.Shared.Return(rentArray);
        }

        public static void Send(this SocketBase socket, ProxyMessage message)
        {
            byte[] rentArray;
            int length = message.ToRentByteArray(out rentArray);
            socket.Send(rentArray, length);
            message.Recycle();
            ArrayPool<byte>.Shared.Return(rentArray);
        }
    }
}";

        public static void GenerateProxy(string type)
        {
            if (!Directory.Exists(xmlPath))
            {
                Debug.Log($"{xmlPath} is not exist, please create!");
                return;
            }
            currentType = type;

            LoadCodeTempletes(type);
            if (Directory.Exists(proxyPath))
            {
                Directory.Delete(proxyPath, true);
            }
            Directory.CreateDirectory(proxyPath);

            GenerateClasses();
            GenerateProxyIndex();
            GenerateProxyManager();
            AssetDatabase.Refresh();
        }

        private static void LoadCodeTempletes(string type)
        {
            protoTemplete = IOUtility.Read($"{proxyTempletePath}/Proto3/Proto.templete");
            messageTemplete = IOUtility.Read($"{proxyTempletePath}/Proto3/Message.templete");
            fieldTemplete = IOUtility.Read($"{proxyTempletePath}/Proto3/Field.templete");
            objectFieldTemplete = IOUtility.Read($"{proxyTempletePath}/Proto3/ObjectField.templete");
            protoMapperXml = new XmlDocument();
            protoMapperXml.Load($"{proxyTempletePath}/Proto3/Mapper.xml");

            proxyIndexTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/ProxyIndex.templete");
            proxyManagerTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/ProxyManager.templete");
            proxyManager_InitializateTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/ProxyManager_Initializate.templete");
            setterFunctionTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/SetterFunction.templete");
            fieldSetterTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/FieldSetter.templete");
            objectFieldSetterTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/ObjectFieldSetter.templete");
            listFieldSetterTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/ListFieldSetter.templete");
            listObjectFieldSetterTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/ListObjectFieldSetter.templete");
            mapFieldSetterTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/MapFieldSetter.templete");
            mapObjectFieldSetterTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/MapObjectFieldSetter.templete");
            keysCheckerTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/KeysChecker.templete");
            containsCheckerTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/ContainsChecker.templete");
            keysGetterTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/KeysGetter.templete");
            objectGetterTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/ObjectGetter.templete");
            containerKeyTemplete = IOUtility.Read($"{proxyTempletePath}/{type}/ContainerKey.templete");

            mapperXml = new XmlDocument();
            mapperXml.Load($"{proxyTempletePath}/{type}/Mapper.xml");

            
        }

        private static bool CheckBaseType(XmlNode fieldXml)
        {
            string type = fieldXml.Attributes["type"].Value;
            return CheckBaseType(type);
        }

        private static bool CheckBaseType(string type)
        {
            return baseType.Contains(type);
        }

        private static bool CheckIsList(string type)
        {
            return type.StartsWith("List[");
        }

        private static bool CheckIsMap(string type)
        {
            return type.StartsWith("Map[");
        }

        private static FieldData GetFieldTypeData(XmlNode fieldXml, XmlDocument mapper)
        {
            string type = fieldXml.Attributes["type"].Value;
            XmlNodeData typeNodeData = FindXmlNode($"/package/class[@name='{type}']");

            if (typeNodeData != null)
            {
                FieldData fieldData = new FieldData(type);
                fieldData.Add(typeNodeData.ProtoFile);
                return fieldData;
            }
            else
            {
                return TransformOriginType(type, mapper);
            }
        }

        private static string GetGenericElementValue(string type, XmlDocument mapper, out List<string> imports)
        {
            int startIndex = type.IndexOf("[") + 1;
            int endIndex = type.LastIndexOf("]");
            string value = type.Substring(startIndex, endIndex - startIndex).Replace(" ", "");
            imports = new List<string>();
            if (CheckIsList(type))
            {
                XmlNodeData xmlNodeData =  FindXmlNode($"/package/class[@name='{value}']");
                if (xmlNodeData != null)
                {
                    imports.Add(xmlNodeData.ProtoFile);
                }
                return TransformBaseType(value, mapper).Type;
            }
            else if (CheckIsMap(type))
            {
                string[] valueArray = value.Split(',');
                XmlNodeData xmlNodeData = FindXmlNode($"/package/class[@name='{valueArray[0]}']");
                if (xmlNodeData != null)
                {
                    imports.Add(xmlNodeData.ProtoFile);
                }
                xmlNodeData = FindXmlNode($"/package/class[@name='{valueArray[1]}']");
                if (xmlNodeData != null)
                {
                    imports.Add(xmlNodeData.ProtoFile);
                }
                return $"{TransformBaseType(valueArray[0], mapper).Type},{TransformBaseType(valueArray[1], mapper).Type}";
            }
            else
            {
                return value;
            }
        }

        private static FieldData TransformGenericType(string type, XmlDocument mapper)
        {
            List<string> imports;
            string origin = type.Substring(0, type.IndexOf("[") + 1) + "]";
            string value = GetGenericElementValue(type, mapper, out imports);
            XmlNode mapperNode = mapper.SelectSingleNode($"mapper/type[@name='{origin}']");
            string result = mapperNode.Attributes["value"].Value;
            FieldData fieldData = new FieldData(imports, result.Replace("#Value#", value));
            return fieldData;
        }

        private static FieldData TransformOriginType(string type, XmlDocument mapper)
        {
            if (CheckIsList(type) || CheckIsMap(type))
            {
                return TransformGenericType(type, mapper);
            }
            else
            {
                return TransformBaseType(type, mapper);
            }
        }

        private static FieldData TransformBaseType(string type, XmlDocument mapper)
        {
            string originKey = type;
            XmlNode mapperNode = mapper.SelectSingleNode($"mapper/type[@name='{originKey}']");
            FieldData fieldData = new FieldData(mapperNode != null ? mapperNode.Attributes["value"].Value : originKey);
            return fieldData;
        }

        private static XmlNodeData FindXmlNode(string xpath)
        {
            XmlNodeData result = null;
            string[] xmlArray = Directory.GetFiles(xmlPath);

            for (int index = 0; index < xmlArray.Length; index++)
            {
                XmlDocument root = new XmlDocument();
                root.Load(xmlArray[index]);
                XmlNode typeNode = root.SelectSingleNode(xpath);
                if (typeNode != null)
                {
                    result = new XmlNodeData(new FileInfo(xmlArray[index]).Name.Replace(".xml", ".proto"), typeNode);
                    break;
                }
            }
            return result;
        }

        public class TypeInfo
        {
            public string type;
            public string keyType;
            public string valueType;
        }

        private static TypeInfo SplitType(string type)
        {
            TypeInfo typeInfo = new TypeInfo();
            if (CheckIsList(type))
            {
                int valueStart = type.IndexOf('[');
                typeInfo.type = type.Substring(0, valueStart);
                typeInfo.keyType = "int";
                typeInfo.valueType = type.Substring(valueStart + 1, type.LastIndexOf(']') - valueStart - 1);
            }
            else if (CheckIsMap(type))
            {
                int valueStart = type.IndexOf('[');
                typeInfo.type = type.Substring(0, valueStart);
                string[] keyValueArray = type.Substring(valueStart + 1, type.LastIndexOf(']') - valueStart - 1).Split(',');
                typeInfo.keyType = keyValueArray[0];
                typeInfo.valueType = keyValueArray[1];

            }
            else
            {
                typeInfo.type = type;
                typeInfo.keyType = null;
                typeInfo.valueType = type;
            }

            return typeInfo;
        }

        #region 生成ProxyClass

        private static void GenerateClasses()
        {
            if (Directory.Exists(proxyDataPath))
            {
                Directory.Delete(proxyDataPath, true);
            }

            Directory.CreateDirectory(proxyDataPath);
            string[] xmlArray = Directory.GetFiles(xmlPath);

            for (int index = 0; index < xmlArray.Length; index++)
            {
                GenerateClass(xmlArray[index]);
            }
            Directory.CreateDirectory($"{proxyDataPath}/BuildInMessages");

            File.Copy($"{proxyTempletePath}/Protobuf/ProtocolMessages.proto", $"{proxyDataPath}/BuildInMessages/ProtocolMessages.proto");
            File.Copy($"{proxyTempletePath}/Protobuf/ProxyMessages.proto", $"{proxyDataPath}/BuildInMessages/ProxyMessages.proto");

            ProtobufGenerator.GenerateProtobuf(currentType, proxyDataPath, $"{proxyPath}/Datas");

            UnityEngine.Debug.Log($"Class Generated.");
        }

        private static void GenerateClass(string xmlName)
        {
            FileInfo fileInfo = new FileInfo(xmlName);
            string resultProtoName = fileInfo.Name.Replace(".xml", ".proto");

            XmlDocument root = new XmlDocument();
            root.Load(xmlName);
            XmlNodeList xmlNodeList = root.GetElementsByTagName("class");
            StringBuilder imports = new StringBuilder();
            StringBuilder messages = new StringBuilder();
            HashSet<string> importSets = new HashSet<string>();
            for (int nodeIndex = 0; nodeIndex < xmlNodeList.Count; nodeIndex++)
            {
                XmlNode xmlNode = xmlNodeList[nodeIndex];
                messages.Append(GenerateMessages(xmlNode, ref importSets));
            }

            HashSet<string>.Enumerator enumerator = importSets.GetEnumerator();
            while (enumerator.MoveNext())
            {
                string importProtoName = enumerator.Current;
                if (importProtoName != resultProtoName)
                {
                    imports.AppendLine($"import \"{importProtoName}\";");
                }
            }

            string result = protoTemplete.Replace("#Imports#", imports.ToString());
            result = result.Replace("#Messages#", messages.ToString());
            IOUtility.Write($"{proxyDataPath}/{resultProtoName}", result);
        }

        private static string GenerateMessages(XmlNode classXml, ref HashSet<string> imports)
        {
            string classResult = messageTemplete.Replace("#Message#", classXml.Attributes["name"].Value);
            string filedString = GetFieldString(classXml, ref imports);
            return classResult.Replace("#FieldTemplete#", filedString);
        }

        private static string GetFieldString(XmlNode classXml, ref HashSet<string> imports)
        {
            StringBuilder fieldResult = new StringBuilder();
            XmlNodeList xmlNodeList = classXml.SelectNodes("field");
            for (int index = 0; index < xmlNodeList.Count; index++)
            {
                XmlNode fieldNode = xmlNodeList[index];
                string templete = CheckBaseType(fieldNode) ? fieldTemplete : objectFieldTemplete;
                FieldData fieldData = GetFieldTypeData(fieldNode, protoMapperXml);
                string fieldString = templete.Replace("#Type#", fieldData.Type);

                for (int importIndex = 0; importIndex < fieldData.Imports.Count; importIndex++)
                {
                    string importProto = fieldData.Imports[importIndex];
                    imports.Add(importProto);
                }

                fieldString = fieldString.Replace("#Field#", fieldNode.Attributes["name"].Value.Substring(0, 1).ToLower() + fieldNode.Attributes["name"].Value.Substring(1));
                fieldString = fieldString.Replace("#Index#", (index + 1).ToString());
                fieldResult.Append(fieldString);
            }
            return fieldResult.ToString();
        }

        #endregion

        #region 生成ProxyIndex

        private static void GenerateProxyIndex()
        {
            string[] xmlArray = Directory.GetFiles(xmlPath);
            StringBuilder proxyIndex = new StringBuilder();
            for (int index = 0; index < xmlArray.Length; index++)
            {
                XmlDocument root = new XmlDocument();
                root.Load(xmlArray[index]);

                XmlNodeList xmlNodeList = root.SelectNodes("/package/class[@tag='root']");
                for (int nodeIndex = 0; nodeIndex < xmlNodeList.Count; nodeIndex++)
                {
                    XmlNode xmlNode = xmlNodeList[nodeIndex];
                    proxyIndex.Append(GetProxyIndexString(xmlNode));
                }
            }

            IOUtility.Write($"{proxyPath}/ProxyIndex.{suffixs[currentType]}", proxyIndexTemplete.Replace("#ProxyIndex#", proxyIndex.ToString()));
            Debug.Log($"ProxyIndex Generated.");
        }

        private static string GetProxyIndexString(XmlNode xmlNode, string prefix = null)
        {
            StringBuilder result = new StringBuilder();
            string className = xmlNode.Attributes["name"].Value;
            if (prefix == null)
            {
                prefix = className;
                result.AppendLine($"{prefix},");
            }

            XmlNodeList xmlNodeList = xmlNode.SelectNodes("field");
            for (int index = 0; index < xmlNodeList.Count; index++)
            {
                XmlNode fieldNode = xmlNodeList[index];
                string name = fieldNode.Attributes["name"].Value;
                name = FirstUpper(name);
                string proxyIndex = $"{prefix}_{name}";
                TypeInfo typeInfo = SplitType(fieldNode.Attributes["type"].Value);
                string type = typeInfo.valueType;
                XmlNode typeNode = FindXmlNode($"/package/class[@name='{type}']")?.Node;

                if (typeNode != null && className != type)
                {
                    result.AppendLine($"{proxyIndex},");
                    proxyIndex = GetProxyIndexString(typeNode, proxyIndex);
                    result.AppendLine($"{proxyIndex}");
                }
                else
                {
                    result.AppendLine($"{proxyIndex},");
                }
            }

            return result.ToString();
        }
        #endregion

        #region 生成ProxyManager

        private static void GenerateProxyManager()
        {
            string[] xmlArray = Directory.GetFiles(xmlPath);
            StringBuilder proxyManagerFieldSetters = new StringBuilder();
            StringBuilder proxyManagerInitializate = new StringBuilder();
            for (int index = 0; index < xmlArray.Length; index++)
            {
                XmlDocument root = new XmlDocument();
                root.Load(xmlArray[index]);

                XmlNodeList xmlNodeList = root.SelectNodes("/package/class[@tag='root']");
                for (int nodeIndex = 0; nodeIndex < xmlNodeList.Count; nodeIndex++)
                {
                    XmlNode xmlNode = xmlNodeList[nodeIndex];
                    proxyManagerFieldSetters.AppendLine(GetFieldSettersString(xmlNode));
                    proxyManagerInitializate.AppendLine(proxyManager_InitializateTemplete.Replace("#ClassName#", xmlNode.Attributes["name"].Value));
                }
            }
            string classResult = proxyManagerTemplete.Replace("#FieldSetters#", proxyManagerFieldSetters.ToString());
            classResult = classResult.Replace("#Initializate#", proxyManagerInitializate.ToString());

            IOUtility.Write($"{proxyPath}/ProxyManager.{suffixs[currentType]}", classResult);
            Debug.Log($"ProxyManager Generated.");
        }

        private static string containerDivider = "#ContainerEnd#";

        private static string GetFieldSettersString(XmlNode classXml, string prefix = null, int containerLevel = 0)
        {
            string className = classXml.Attributes["name"].Value;

            if (prefix == null)
            {
                prefix = className;
            }

            StringBuilder fieldSetterResult = new StringBuilder();
            XmlNodeList xmlNodeList = classXml.SelectNodes("field");
            for (int index = 0; index < xmlNodeList.Count; index++)
            {
                XmlNode fieldNode = xmlNodeList[index];
                string name = fieldNode.Attributes["name"].Value;
                name = FirstUpper(name);
                string proxyIndex = $"{prefix}_{name}";
                string type = fieldNode.Attributes["type"].Value;
                XmlNode typeNode = FindXmlNode($"/package/class[@name='{type}']")?.Node;

                List<string> keyTypes;
                List<string> objectStrings = ProxyIndexToObjectList(proxyIndex, containerLevel, out keyTypes);

                if (typeNode != null)
                {
                    fieldSetterResult.AppendLine(GetFieldSetterString(GetSetterContent(objectFieldSetterTemplete, type, objectStrings.Last(), containerLevel), proxyIndex, containerLevel, objectStrings, keyTypes));
                    if (className != type)
                    {
                        fieldSetterResult.AppendLine(GetFieldSettersString(typeNode, proxyIndex, containerLevel));
                    }
                }
                else
                {
                    if (CheckBaseType(type))
                    {
                        fieldSetterResult.AppendLine(GetFieldSetterString(GetSetterContent(fieldSetterTemplete, type, objectStrings.Last(), containerLevel), proxyIndex, containerLevel, objectStrings, keyTypes));
                    }
                    else
                    {
                        TypeInfo typeInfo = SplitType(type);

                        if (CheckIsList(type))
                        {
                            bool isBase = CheckBaseType(typeInfo.valueType);
                            string genericTemplete = isBase ? listFieldSetterTemplete : listObjectFieldSetterTemplete;
                            fieldSetterResult.AppendLine(GetFieldSetterString(GetSetterContent(genericTemplete, typeInfo.valueType, objectStrings.Last(), containerLevel), proxyIndex, containerLevel, objectStrings, keyTypes));
                            if (!isBase && className != typeInfo.valueType)
                            {
                                typeNode = FindXmlNode($"/package/class[@name='{typeInfo.valueType}']")?.Node;

                                proxyIndex = $"{proxyIndex}${typeInfo.keyType}${containerDivider}";
                                fieldSetterResult.AppendLine(GetFieldSettersString(typeNode, proxyIndex, containerLevel + 1));
                            }
                        }
                        else if (CheckIsMap(type))
                        {
                            bool isBase = CheckBaseType(typeInfo.valueType);
                            string genericTemplete = isBase ? mapFieldSetterTemplete : mapObjectFieldSetterTemplete;

                            fieldSetterResult.AppendLine(GetFieldSetterString(GetMapSetterContent(genericTemplete, typeInfo.keyType, typeInfo.valueType, objectStrings.Last(), containerLevel), proxyIndex, containerLevel + 1, objectStrings, keyTypes));
                            if (!isBase && className != typeInfo.valueType)
                            {
                                typeNode = FindXmlNode($"/package/class[@name='{typeInfo.valueType}']")?.Node;
                                proxyIndex = $"{proxyIndex}${typeInfo.keyType}${containerDivider}";
                                fieldSetterResult.AppendLine(GetFieldSettersString(typeNode, proxyIndex, containerLevel + 1));
                            }
                        }
                    }
                }
            }
            return fieldSetterResult.ToString();

        }

        private static List<string> ProxyIndexToObjectList(string proxyIndex, int containerLevel, out List<string> keyTypes)
        {
            List<string> result = new List<string>();
            keyTypes = new List<string>();
            int currentLevel = 0;
            int containerDividerLength = containerDivider.Length;
            proxyIndex = proxyIndex.Replace("_", ".");
            string className = proxyIndex.Substring(0, proxyIndex.IndexOf("."));
            string objectGetter = objectGetterTemplete.Replace("#ClassName#", className);
            string fieldString = objectGetter.Replace("#Field#", "") + proxyIndex.Substring(proxyIndex.IndexOf(".") + 1);

            bool isEndWithContainer = fieldString.EndsWith(containerDivider);
            while (currentLevel < containerLevel)
            {
                string objectStringTemplete = fieldString.Substring(0, fieldString.IndexOf(containerDivider) + containerDividerLength);
                string keyGetter = keysGetterTemplete.Replace("#Index#", currentLevel.ToString());
                int typeBgein = objectStringTemplete.IndexOf("$") + 1;
                string keyType = objectStringTemplete.Substring(typeBgein, objectStringTemplete.LastIndexOf("$") - typeBgein);

                keyGetter = keyGetter.Replace("#Type#", FirstUpper(keyType));
                keyTypes.Add(keysGetterTemplete.Replace("#Index#", currentLevel.ToString()).Replace("#Type#", FirstUpper(keyType)));
                string objectString = objectStringTemplete.Replace(containerDivider, "").Replace($"${keyType}$", "");
                fieldString = fieldString.Replace(objectStringTemplete, objectStringTemplete.Replace(containerDivider, containerKeyTemplete.Replace("#Key#", keyGetter)).Replace($"${keyType}$", ""));
                result.Add(objectString);
                currentLevel++;
            }

            if (!isEndWithContainer)
            {
                result.Add(fieldString);
            }

            return result;
        }

        private static string FirstUpper(string value)
        {
            return value.Substring(0, 1).ToUpper() + value.Substring(1); ;
        }

        private static string GetSetterContent(string templete, string type, string objectName, int containerLevel)
        {
            string fieldSetterString = templete.Replace("#Object#", objectName);

            string firstUpperType = FirstUpper(type);
            fieldSetterString = fieldSetterString.Replace("#Type#", firstUpperType);
            fieldSetterString = fieldSetterString.Replace("#type#", type);
            fieldSetterString = fieldSetterString.Replace("#ContainerLevel#", containerLevel.ToString());
            return fieldSetterString;
        }

        private static string GetMapSetterContent(string templete, string key, string value, string objectName, int containerLevel)
        {
            string fieldSetterString = templete.Replace("#Object#", objectName);
            fieldSetterString = fieldSetterString.Replace("#keyType#", key);
            fieldSetterString = fieldSetterString.Replace("#valueType#", value);

            string firstUpperKey = FirstUpper(key);
            fieldSetterString = fieldSetterString.Replace("#KeyType#", firstUpperKey);
            string firstUpperValue = FirstUpper(value);
            fieldSetterString = fieldSetterString.Replace("#ValueType#", firstUpperValue);

            fieldSetterString = fieldSetterString.Replace("#ContainerLevel#", containerLevel.ToString());

            return fieldSetterString;
        }

        private static string GetFieldSetterString(string fieldSetterString, string proxyIndex, int containerLevel, List<string> objectStrings, List<string> keyTypes)
        {
            string realProxyIndex = proxyIndex.Replace(containerDivider, "");
            Regex regex = new Regex(@"\$\w{3,6}\$");
            realProxyIndex = regex.Replace(realProxyIndex, "");
            string setterFunction = setterFunctionTemplete.Replace("#ProxyIndex#", realProxyIndex);

            setterFunction = setterFunction.Replace("#SetterContent#", fieldSetterString);
            if (containerLevel == 0)
            {
                setterFunction = setterFunction.Replace("#KeysChecker#", "");
                setterFunction = setterFunction.Replace("#ContainsChecker#", "");
            }
            else
            {
                setterFunction = setterFunction.Replace("#KeysChecker#", keysCheckerTemplete.Replace("#Num#", containerLevel.ToString()));
                StringBuilder containsCheckers = new StringBuilder();
                for (int i = 0; i < objectStrings.Count - 1; i++)
                {
                    string containsChecker = containsCheckerTemplete.Replace("#Object#", objectStrings[i]);
                    containsChecker = containsChecker.Replace("#Key#", keyTypes[i]);
                    containsCheckers.AppendLine(containsChecker);
                }
                setterFunction = setterFunction.Replace("#ContainsChecker#", containsCheckers.ToString());
            }

            return setterFunction;
        }
        #endregion
    }
}
